module genapi;

import std.meta,
std.traits;

struct type { // @suppress(dscanner.style.phobos_naming_convention)
	string name;
}

/// The summary of the function.
struct summary { // @suppress(dscanner.style.phobos_naming_convention)
	string content;
}

enum ignored(alias x) = hasUDA!(x, ignore);
private enum hasName(alias x) = __traits(compiles, (string s) { s = x.name; });
enum isString(alias x) = is(typeof(x) : const(char)[]);

enum isExport(alias f) =
	__traits(getLinkage, f) == "C" && __traits(getVisibility, f) == "export";

enum KeyName(alias T, string defaultName = T.stringof) = defaultName;

template ForModules(T...) {
	void genAPIDef(alias getType = TSTypeOf, R)(ref R sink) {
		foreach (m; T) {
			foreach (name; __traits(allMembers, m)) {
				alias f = __traits(getMember, m, name);
				static if (is(typeof(&f)) && isExport!f) {
					sink.put("/**\n");
					getDoc!f(sink);
					sink.put(" */\n");
					sink.put(__traits(identifier, f));
					static if (isFunction!f) {
						sink.put('(');
						getArgs!(f, getType)(sink);
						sink.put("): ");
						static if (hasUDA!(f, type))
							sink.put(getUDAs!(f, type)[0].name);
						else
							sink.put(getType!(ReturnType!f));
					} else
						sink.put(": WebAssembly.Global");
					sink.put('\n');
				}
			}
		}
	}
}

void getDoc(alias f, R)(ref R sink) {
	alias loc = __traits(getLocation, f);
	if (loc[1] > 1) {
		sink.put(" *");
		foreach (attr; getUDAs!(f, summary)) {
			sink.put(attr.content);
		}
		sink.put(getComment(loc[0], loc[1] - 1));
		sink.put('\n');
	}
	static if (is(FunctionTypeOf!f P == __parameters)) {
		alias PIT = ParameterIdentifierTuple!f;
		alias set = Filter!(isString, __traits(getAttributes, f));
		static foreach (i, T; P) {
			{
				alias p = P[i .. i + 1];
				sink.put(" * @param ");
				sink.put(KeyName!(p, PIT[i].length ? PIT[i] : "arg" ~ i.stringof));
				sink.put(' ');
				static foreach (attr; __traits(getAttributes, p))
					static if (isString!attr && staticIndexOf!(attr, set) == -1)
						sink.put(attr);
				sink.put('\n');
			}
		}
		if (set.length)
			sink.put(" * @returns ");
		foreach (attr; set)
			static if (isString!attr)
				sink.put(attr);
		if (set.length)
			sink.put('\n');
	}
}

template getParamUDAs(alias attr, alias f, attrs...) {
	alias fAttr = __traits(getAttributes, f);
	alias getParamUDAs = AliasSeq!();
	static foreach (a; attrs) {
		static if (staticIndexOf!(a, fAttr) == -1) {
			static if (__traits(isSame, a, attr))
				getParamUDAs = AliasSeq!(getParamUDAs, a);
			else static if (is(typeof(a)))
				static if (is(typeof(a) == attr))
					getParamUDAs = AliasSeq!(getParamUDAs, a);
		}
	}
}

void getArgs(alias f, alias getType = TSTypeOf, R)(ref R sink) {
	static if (is(typeof(f) P == __parameters)) {
		alias PIT = ParameterIdentifierTuple!f;
		static foreach (i, T; P) {
			{
				alias p = P[i .. i + 1];
				sink.put(KeyName!(p, PIT[i].length ? PIT[i] : "arg" ~ i.stringof));
				static if (!is(ParameterDefaults!f[i] == void))
					sink.put('?');
				sink.put(": ");
				alias a = getParamUDAs!(type, f, __traits(getAttributes, p));
				static if (a.length)
					sink.put(a[0].name);
				else
					sink.put(getType!p);
				static if (i + 1 < P.length)
					sink.put(", ");
			}
		}
	}
}

/// Get the type name of the given type.
template TSTypeOf(T) if (is(T == enum)) {
	enum TSTypeOf = TSTypeOf!(OriginalType!T);
}

/// ditto
template TSTypeOf(T) if (!is(T == enum)) {
	static if (hasUDA!(T, type) && hasName!(getUDAs!(T, type)[0]))
		enum TSTypeOf = getUDAs!(T, type)[0].name;
	else static if (is(T == U[], U)) {
		static if (is(T : const(char)[]))
			enum TSTypeOf = "string";
		else
			enum TSTypeOf = TSTypeOf!U ~ "[]";
	} else static if (isNumeric!T || isPointer!T)
		enum TSTypeOf = "number";
	else static if (isBoolean!T)
		enum TSTypeOf = "boolean";
	else static if (isSomeChar!T)
		enum TSTypeOf = "string";
	else static if (is(T == void))
		enum TSTypeOf = "void";
	else static if (is(T : typeof(null)))
		enum TSTypeOf = "null";
	else
		enum TSTypeOf = "any";
}

/// Get the comment at the given line and column.
char[] getComment(string filename, uint line, uint col = 1) {
	import std.stdio,
	std.string;

	--line;
	--col;
	uint i;
	try {
		foreach (l; File(filename).byLine) {
			if ((i + 1 == line || i == line) && l.length >= col) {
				l = l[col .. $].stripLeft;
				if (l.startsWith("///"))
					return l[3 .. $];
			}
			++i;
		}
	} catch (Exception) {
	}
	return null;
}
